#include "Core/Shader.h"
#include "Core/MediaManager.h"
#include "Core/Renderer.h"
#include "Debug/Logger.h"

namespace blue {

Shader::Shader(ShaderBase *vtshader, ShaderBase *pcshader) :
_vtshader(vtshader), _pcshader(pcshader), _created(false)
{
    if(_vtshader && _pcshader)
        create();
}

Shader::Shader(const char *vtname, const char *pcname) :
_created(false)
{
    if(vtname && pcname)
    {
        MediaManager *mmgr = MediaManager::instance();
        _vtshader = mmgr->getMedia<ShaderBase>(vtname);
        _pcshader = mmgr->getMedia<ShaderBase>(pcname);

        if(_vtshader && _pcshader)
            create();
    }
}

Shader::~Shader()
{
    if(glIsProgramARB(_program))
      {
	glDetachObjectARB(_program, _vtshader->getGLID());
	glDetachObjectARB(_program, _pcshader->getGLID());
        glDeleteObjectARB(_program);
      }
    if(_vtshader)
        _vtshader->release();
    if(_pcshader)
        _pcshader->release();
}

void Shader::load(const char *vtname, const char *pcname)
{
  if(glIsProgramARB(_program))
      {
	glDetachObjectARB(_program, _vtshader->getGLID());
	glDetachObjectARB(_program, _pcshader->getGLID());
        glDeleteObjectARB(_program);
      }
    if(_vtshader)
    {
        _vtshader->release();
        _vtshader = NULL;
    }
    if(_pcshader)
    {
        _pcshader->release();
        _pcshader = NULL;
    }
    _created = false;
     MediaManager *mmgr = MediaManager::instance();
    _vtshader = mmgr->getMedia<ShaderBase>(vtname);
    _pcshader = mmgr->getMedia<ShaderBase>(pcname);
    create();
}

void Shader::create()
{
    GLint link_status = GL_TRUE;
    GLint logsize = 0;
    char *log = NULL;

    _program = glCreateProgramObjectARB();

    if(_vtshader)
        glAttachObjectARB(_program, _vtshader->getGLID());
    if(_pcshader)
        glAttachObjectARB(_program, _pcshader->getGLID());

    glLinkProgramARB(_program);

    glGetObjectParameterivARB(_program, GL_LINK_STATUS, &link_status);
    if(link_status != GL_TRUE)
    {
        glGetObjectParameterivARB(_program, GL_INFO_LOG_LENGTH, &logsize);
        log = new char[logsize + 1];

        memset(log, '\0', logsize + 1);
        glGetInfoLogARB(_program, logsize, &logsize, log);

        Logger::log() << "Couldn't create shader : " << log << "\n";

        delete[] log;
        glDeleteObjectARB(_program);
    }
    else
        _created = true;
}

void Shader::setShader(bool set)
{
    if(_created)
    {
        if(set) Renderer::instance()->setShader(this);
        else Renderer::instance()->setShader(NULL);
    }
}

bool Shader::isShader()
{
    return _created;
}

GLuint Shader::getProgram() const
{
    return _program;
}

ShaderBase *Shader::getVertexShader() const
{
    return _vtshader;
}

ShaderBase *Shader::getPixelShader() const
{
    return _pcshader;
}

void Shader::SetParameter(const char *name, int value)
{
    int id = glGetUniformLocationARB(_program, name);
    glUniform1iARB(id, value);
}

void Shader::SetParameter(const char *name, float value)
{
    int id = glGetUniformLocationARB(_program, name);
    glUniform1fARB(id, value);
}

void Shader::SetParameter(const char *name, const Vector2f& value)
{
    int id = glGetUniformLocationARB(_program, name);
    float vec[2] = {value.X, value.Y};
    glUniform2fvARB(id, 1, vec);
}

void Shader::SetParameter(const char *name, const Vector3f& value)
{
    int id = glGetUniformLocation(_program, name);
    float vec[3] = {value.X, value.Y, value.Z};
    glUniform3fvARB(id, 1, vec);
}

void Shader::SetParameter(const char *name, const Color4f& value)
{
    int id = glGetUniformLocationARB(_program, name);
    float vec[4] = {value.R, value.G, value.B, value.A};
    glUniform4fvARB(id, 1, vec);
}

}
